<?php

class Emms_Loan
{
    private $_loan;
    
    private $_interestCalculation;
    private $_installments;
    //private $_effectiveRate;
    private $_annualPercentageRate;
    private $_loanInsurancePct;
    private $_calendarType;
    private $_paymentFrequency;
    private $_deliveryDate;
    private $_firstPaymentDate;
    private $_healthInsurancePct;
    private $_administrativeFeePct;
    private $_principalGraceRate;
    private $_principal;
    private $_principalGraceEndDate;
    private $_principalGraceMargin;
    private $_principalGrace;
    private $_delayGrace;
    private $_delayPenaltyRate;
    private $_delayPenaltyFixedCharge;
    private $_term;
    private $_pmt;
    private $_gracePmt;
    
    public function __construct() {
        $args = func_get_args(); 
        if(isset($args[0])&&is_array($args[0])) {
            $this->_constructFromArray($args[0]);
        } elseif(isset($args[0])&&is_numeric($args[0])) {
            $loans = new Loans();
            try {
                $this->_loan = $loans->getRecord($args[0]); 
                $this->_constructFromArray($this->_loan->d->toArray());
            } catch (Exception $e) {
                throw new Exception($e->getMessage()); 
            }
        } else {
            throw new Exception($this->translate("Invalid loan code or parameters, the payment plan couldn't be created.")); 
        }
    }
    
    protected function _constructFromArray($values)
    {
        /*
             ->_setDeliveryDate(isset($values['InterestFromDate']) ? $values['InterestFromDate'] :
                 isset($values['DeliveryDate']) ? $values['DeliveryDate'] : $values['TentativeDeliveryDate'])
         */
        //deliveryDate
        if(isset($values['InterestFromDate']))
            $dd = $values['InterestFromDate'];
        elseif(isset($values['DeliveryDate']))
            $dd = $values['DeliveryDate'];
        else 
            $dd = $values['TentativeDeliveryDate'];
        
        /*
                     ->_setFirstPaymentDate(
                     isset($values['NextPaymentDate']) ? $values['NextPaymentDate'] : 
                         isset($values['ExpectedFirstPaymentDate']) ? $values['ExpectedFirstPaymentDate'] : 
                             isset($values['TentativeFirstPaymentDate']) ? $values['TentativeFirstPaymentDate'] : 
                                 $values['FirstPaymentDate'])
         */
        //firstPaymentDate
        if(isset($values['NextPaymentDate']))
            $fpd = $values['NextPaymentDate'];
        elseif(isset($values['ExpectedFirstPaymentDate']))
            $fpd = $values['ExpectedFirstPaymentDate'];
        elseif(isset($values['TentativeFirstPaymentDate']))
            $fpd = $values['TentativeFirstPaymentDate'];
        else 
            $fpd = $values['FirstPaymentDate'];
        
        $this->_setInterestCalculation($values['InterestCalculation'])
             ->_setCalendarType($values['CalendarType'])
             ->_setInstallments($values['Installments'])
             ->_setPaymentFrequency($values['PaymentFrequency'])
             ->_setDeliveryDate($dd)
             ->_setFirstPaymentDate($fpd)
             ->_setAnnualPercentageRate($values['AnnualPercentageRate'])
             ->_setLoanInsurancePct($values['LoanInsurancePct'])
             ->_setHealthInsurancePct($values['HealthInsurancePct'])
             ->_setAdministrativeFeePct($values['AdministrativeFeePct'])
             ->_setPrincipalGraceRate($values['PrincipalGraceRate'])
             ->_setPrincipal($values['Principal'])
             ->_setPrincipalGraceEndDate()
             ->_setPrincipalGraceMargin($values['PrincipalGraceMargin'])
             ->_setPrincipalGrace()
             ->_setDelayGrace($values['DelayGrace'])
             ->_setDelayPenaltyRate($values['DelayPenaltyRate'])
             ->_setDelayPenaltyFixedCharge($values['DelayPenaltyFixedCharge'])
             ->_setTerm()
             ->_setPmt()
             ->_setGracePmt();
    }
         
    protected function _setInterestCalculation($interestCalculation)
    {        
        switch($interestCalculation) {
            case "Declining"; case "Flat"; break;
            default: throw new Exception($this->translate("Invalid interest calculation method"));
        }
        $this->_interestCalculation = $interestCalculation;
        return $this;
    }
         
    protected function _setCalendarType($calendarType)
    {
        if(($calendarType != 360)&&($calendarType != 365)) 
            throw new Exception($this->translate("Invalid calendar type"));
        $this->_calendarType = $calendarType;
        return $this;
    }
         
    protected function _setInstallments($installments)
    {
        if((!is_numeric($installments))||($installments <= 0)) 
            throw new Exception($this->translate("Invalid number of installments"));
        $this->_installments = $installments;
        return $this;
    }
         
    protected function _setPaymentFrequency($paymentFrequency)
    {        
        try {
            self::validatePaymentFrequency($paymentFrequency);
        } catch (Exception $e) {
            throw new Exception($e->getMessage()); 
        }
        $this->_paymentFrequency = $paymentFrequency;
        return $this;
    }
    
    protected function _setDeliveryDate($deliveryDate)
    {
        try {
            self::validateDeliveryDate($deliveryDate);
        } catch (Exception $e) {
            throw new Exception($e->getMessage()); 
        }
        $this->_deliveryDate = $deliveryDate;
        return $this;
    }
    
    protected function _setFirstPaymentDate($firstPaymentDate)
    {  
        if(!$this->_loan||!$this->_loan->d->DeliveryDate) {
            try {
                self::validateFirstPaymentDate($firstPaymentDate);
            } catch (Exception $e) {
                throw new Exception($e->getMessage()); 
            }
        }
        if($firstPaymentDate <= $this->_deliveryDate)
            throw new Exception($this->translate("Invalid first payment date"));       
        $this->_firstPaymentDate = $firstPaymentDate;
        return $this;
    }
        
    protected function _setAnnualPercentageRate($annualPercentageRate)
    {
        if((!is_numeric($annualPercentageRate))||($annualPercentageRate < 0)) 
            throw new Exception($this->translate("Invalid annual percentage rate"));
        $this->_annualPercentageRate = $annualPercentageRate;
        return $this;
    }
         
    protected function _setLoanInsurancePct($loanInsurancePct)
    {
        if((!is_numeric($loanInsurancePct))||($loanInsurancePct < 0)) 
            throw new Exception($this->translate("Invalid loan insurance fee"));
        $this->_loanInsurancePct = $loanInsurancePct;
        return $this;
    }
         
    protected function _setHealthInsurancePct($healthInsurancePct)
    {
        if((!is_numeric($healthInsurancePct))||($healthInsurancePct < 0)) 
            throw new Exception($this->translate("Invalid health insurance fee"));
        $this->_healthInsurancePct = $healthInsurancePct;
        return $this;
    }
    
    protected function _setAdministrativeFeePct($administrativeFeePct)
    {
        if((!is_numeric($administrativeFeePct))||($administrativeFeePct < 0)) 
            throw new Exception($this->translate("Invalid administrative fee"));
        $this->_administrativeFeePct = $administrativeFeePct;
        return $this;
    }
    
    protected function _setPrincipalGraceRate($principalGraceRate)
    {
        if((!is_numeric($principalGraceRate))||($principalGraceRate < 0)) 
            throw new Exception($this->translate("Invalid principal grace rate"));
        $this->_principalGraceRate = $principalGraceRate;
        return $this;
    }
    
    protected function _setPrincipal($principal)
    {
        if((!is_numeric($principal))||($principal <= 0)) 
            throw new Exception($this->translate("Invalid principal"));
        $this->_principal = $principal;
        return $this;
    }
            
    protected function _setPrincipalGraceEndDate()
    {
        $date = new Zend_Date($this->_firstPaymentDate,'Y-m-d');
        switch($this->_paymentFrequency) {
            case "W" : $date->subDay(7);   break;
            case "BW": $date->subDay(14);  break;
            case "M" : $date->subMonth(1); break;
            case "Q" : $date->subMonth(3); break;
            case "SA": $date->subMonth(6); break;
            case "A" : $date->subYear(1);  break;
        }
        $this->_principalGraceEndDate = ($date->compareDate($this->_deliveryDate, 'Y-m-d') > 0) ?
                $date->get('Y-m-d') : $this->_deliveryDate;
        return $this;
    }
    
    protected function _setPrincipalGraceMargin($principalGrace)
    {     
        try {
            self::validatePrincipalGraceMargin($principalGrace);
        } catch (Exception $e) {
            throw new Exception($e->getMessage()); 
        }     
        $this->_principalGraceMargin = $principalGrace;
        return $this;
    }
    
    protected function _setPrincipalGrace()
    {        
        $date = new Zend_Date($this->_principalGraceEndDate, 'Y-m-d');
        $n = ceil($date->sub(new Zend_Date($this->_deliveryDate,'Y-m-d'))->toValue()/60/60/24);
        if(!$this->_loan||!$this->_loan->d->DeliveryDate) {
            if($n > $this->_principalGraceMargin) 
                throw new Exception($this->translate("Principal grace margin exceeded"));
        }
        $this->_principalGrace = $n;
        return $this;
    }
    
    protected function _setDelayGrace($delayGrace)
    {        
        if((!is_numeric($delayGrace))||($delayGrace < 0)) 
            throw new Exception($this->translate("Invalid delay grace"));        
        $this->_delayGrace = $delayGrace;
        return $this;
    }
        
    protected function _setDelayPenaltyRate($delayPenaltyRate)
    {
        if((!is_numeric($delayPenaltyRate))||($delayPenaltyRate < 0)) 
            throw new Exception($this->translate("Invalid delay penalty rate"));
        $this->_delayPenaltyRate = $delayPenaltyRate;
        return $this;
    }
    
    protected function _setDelayPenaltyFixedCharge($delayPenaltyFixedCharge)
    {
        if((!is_numeric($delayPenaltyFixedCharge))||($delayPenaltyFixedCharge < 0)) 
            throw new Exception($this->translate("Invalid delay penalty fixed charge"));
        $this->_delayPenaltyFixedCharge = $delayPenaltyFixedCharge;
        return $this;
    }
    
    protected function _setTerm()
    {
        $date = $this->_firstPaymentDate;
        for($i=1;$i<$this->_installments;$i++) {
           $date = Emms_PmtPlan::nextPaymentDate($date,$this->_paymentFrequency); 
        }
        $d1 = new Zend_Date($this->_principalGraceEndDate,'Y-m-d');
        $d2 = new Zend_Date($date,'Y-m-d'); 
        $this->_term = ceil($d2->sub($d1)->toValue()/60/60/24);        
        return $this;
    }

    protected function _setPmt()
    {
        if ($this->_loan) {
            $this->_pmt = $this->_loan->d->PaymentMnt;
            return $this;
        }
        $kpTotal = $this->_principal * ((100 + $this->_loanInsurancePct + $this->_healthInsurancePct + $this->_administrativeFeePct)/100);  
        switch($this->_interestCalculation) {
            case "Declining": 
                $i = (($this->_annualPercentageRate/100)*$this->_term)/($this->_calendarType*$this->_installments);
                $this->_pmt = round(($kpTotal * $i)/(1-pow((1/(1+$i)),$this->_installments)),2);
                break;
            case "Flat":
                $i = (($this->_annualPercentageRate/100)*$this->_term)/($this->_calendarType);      
                $this->_pmt = round(($kpTotal*(1 + $i)/$this->_installments),2);
                break;
        }
        return $this;   
    }
    
    protected function _setGracePmt()
    {
        $balance = $this->_principal +
                0.01 * $this->_principal * $this->_healthInsurancePct +
                0.01 * $this->_principal * $this->_loanInsurancePct +
                0.01 * $this->_principal * $this->_administrativeFeePct;
        $this->_gracePmt = round((($balance)*0.01*($this->_principalGraceRate/$this->_calendarType)*$this->_principalGrace),2);    
        return $this;
    }
    
    public function getPrincipalGraceEndDate()
    {
        return $this->_principalGraceEndDate;
    }
    
    public function getPrincipalGraceInterestCharged()
    {
        return $this->_gracePmt;
    }
    
    public function getPmt()
    {
        return $this->_pmt;
    }
    
    
    public function getTentativeCancelDate()
    {
        $d = new Zend_Date($this->_principalGraceEndDate,'Y-m-d'); 
        return $d->addDay($this->_term)->toString('Y-m-d', 'php');  
    }
    
    public function toArray() 
    {
        return array(
            'Loan' => $this->_loan,
            'InterestCalculation' => $this->_interestCalculation,
            'CalendarType' => $this->_calendarType,
            'Installments' => $this->_installments,
            'PaymentFrequency' => $this->_paymentFrequency,
            'DeliveryDate' => $this->_deliveryDate,
            'FirstPaymentDate' => $this->_firstPaymentDate,
            'AnnualPercentageRate' => $this->_annualPercentageRate,
            'LoanInsurancePct' => $this->_loanInsurancePct,
            'HealthInsurancePct' => $this->_healthInsurancePct,
            'AdministrativeFeePct' => $this->_administrativeFeePct,
            'PrincipalGraceRate' => $this->_principalGraceRate,
            'Principal' => $this->_principal,
            'PrincipalGraceEndDate' => $this->_principalGraceEndDate,
            'PrincipalGraceMargin' => $this->_principalGraceMargin,
            'PrincipalGrace' => $this->_principalGrace,
            'DelayGrace' => $this->_delayGrace,
            'DelayPenaltyRate' => $this->_delayPenaltyRate,
            'DelayPenaltyFixedCharge' => $this->_delayPenaltyFixedCharge,
            'Term' => $this->_term,
            'Pmt' => $this->_pmt,
            'GracePmt' => $this->_gracePmt
        );
    }
    
    public function translate($value)
    {            
        return Zend_Registry::get('Zend_Translate')->_($value);
    }
    
    static function validateDeliveryDate($date)
    { 
        try {
            $hollidays = Emms_Date::isHollidays($date);
        } catch (Exception $e) {
            throw new Exception(Zend_Registry::get('Zend_Translate')->_("Invalid delivery date") . ". " . $e->getMessage()); 
        } 
        if ($hollidays)
            throw new Exception(Zend_Registry::get('Zend_Translate')->_("Delivery date can't be hollidays"));
    }
    
    static function validateFirstPaymentDate($date)
    { 
        try {
            $hollidays = Emms_Date::isHollidays($date);
        } catch (Exception $e) {
            throw new Exception(Zend_Registry::get('Zend_Translate')->_("Invalid first payment date") . ". " . $e->getMessage()); 
        } 
        if ($hollidays)
            throw new Exception(Zend_Registry::get('Zend_Translate')->_("First payment date can't be hollidays"));
    }
        
    static function validatePrincipalGraceMargin($principalGrace)
    {        
        if((!is_numeric($principalGrace))||($principalGrace < 0)) 
            throw new Exception(Zend_Registry::get('Zend_Translate')->_("Invalid principal grace"));
    }
      
    static function validatePaymentFrequency($paymentFrequency)
    {        
        switch($paymentFrequency) {
            case "W"; case "BW"; case "M"; case "Q"; case "SA"; case "A"; break;
            default: throw new Exception(Zend_Registry::get('Zend_Translate')->_("Invalid payment frequency"));
        }
    }
}
